# -*- coding: utf-8 -*-
# @author: HRUN

import os
import subprocess
from datetime import datetime
from celery import shared_task

from performance.models import PerformanceTask
from performance.serializers import PerformanceTaskRunSerializer
from performanceengine.params import save_data


# 全局IP缓存
_host_ip_cache = None

def get_host_ip():
    """优先获取内网IP（10/172.16-31/192.168），其次获取公网IP，最后localhost"""
    global _host_ip_cache
    
    # 如果已经缓存了IP，直接返回
    if _host_ip_cache is not None:
        return _host_ip_cache
    
    import socket
    import requests
    import re
    import subprocess
    import os

    # 1. 优先获取内网IP
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
        s.close()
        # 判断是否为内网IP
        if (
            ip.startswith('192.168.') or
            ip.startswith('10.') or
            re.match(r'^172\\.(1[6-9]|2[0-9]|3[0-1])\\.', ip)
        ):
            _host_ip_cache = ip
            return ip
    except Exception as e:
        print(f"通过socket获取内网IP失败: {e}")

    # 2. 尝试多个公网IP服务
    ip_services = [
        'https://ipinfo.io/ip',
        'https://api.ipify.org',
        'https://icanhazip.com',
        'https://ifconfig.me/ip',
        'https://checkip.amazonaws.com'
    ]
    
    for service_url in ip_services:
        try:
            response = requests.get(service_url, timeout=5)
            if response.status_code == 200:
                ip = response.text.strip()
                if ip and not ip.startswith('127.'):
                    print(f"成功从 {service_url} 获取公网IP: {ip}")
                    _host_ip_cache = ip
                    return ip
        except Exception as e:
            print(f"通过 {service_url} 获取公网IP失败: {e}")
            continue

    # 3. 尝试从网络接口获取IP
    try:
        result = subprocess.run(['hostname', '-I'], capture_output=True, text=True, timeout=5)
        if result.returncode == 0:
            ips = result.stdout.strip().split()
            for ip in ips:
                if ip and not ip.startswith('127.') and not ip.startswith('::'):
                    print(f"通过hostname获取到IP: {ip}")
                    _host_ip_cache = ip
                    return ip
    except Exception as e:
        print(f"通过hostname获取IP失败: {e}")

    # 4. 尝试从环境变量获取
    host_ip = os.environ.get('HOST_IP') or os.environ.get('SERVER_IP')
    if host_ip:
        print(f"从环境变量获取到IP: {host_ip}")
        _host_ip_cache = host_ip
        return host_ip

    # 5. 兜底方案
    print("警告: 无法获取有效的IP地址，使用localhost")
    _host_ip_cache = 'localhost'
    return 'localhost'

# 全局端口锁定机制
_port_locks = {}
_active_processes = {}  # 记录活跃的进程和端口
import threading

# 添加一个简单的端口分配机制
def get_next_available_port():
    """使用更简单但更可靠的端口分配方法"""
    import socket
    import random
    import time
    
    # 添加随机延迟，减少并发冲突
    time.sleep(random.uniform(0.1, 0.3))
    
    # 从8089开始查找可用端口
    for port in range(8089, 8139):  # 50个端口
        try:
            # 尝试绑定端口来检查是否可用
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind(('0.0.0.0', port))
            sock.close()
            return port
        except OSError:
            continue
    
    # 如果所有端口都被占用，强制清理第一个端口
    print("端口冲突，清理8089端口")
    kill_process_using_port(8089)
    time.sleep(1)
    return 8089

def find_available_port(start_port=8089, max_attempts=50):
    """查找可用端口，使用更简单但更可靠的方法"""
    import socket
    import time
    import random
    
    # 使用更大的随机延迟，减少并发冲突
    time.sleep(random.uniform(0.5, 1.5))
    
    for port in range(start_port, start_port + max_attempts):
        try:
            # 尝试绑定端口来检查是否可用
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind(('0.0.0.0', port))
            sock.close()

            return port
        except OSError:
            continue
    
    # 如果所有端口都被占用，强制清理并重试
    print(f"端口冲突，清理端口 {start_port}")
    try:
        kill_process_using_port(start_port)
        # 等待一下让进程完全退出
        time.sleep(3)
        
        # 再次尝试绑定端口
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind(('0.0.0.0', start_port))
        sock.close()
        
        print(f"清理后端口 {start_port} 可用")
        return start_port
    except Exception as e:
        print(f"端口清理失败: {e}")
        raise Exception(f"无法找到可用端口，请手动清理端口占用")

def release_port(port):
    """释放端口锁定"""
    if port in _port_locks:
        try:
            _port_locks[port].release()
            del _port_locks[port]
            print(f"释放端口锁定: {port}")
        except:
            pass

def cleanup_process_port(pid):
    """清理进程占用的端口"""
    if pid in _active_processes:
        port = _active_processes[pid]
        del _active_processes[pid]
        print(f"清理进程 {pid} 占用的端口 {port}")
        
        # 检查进程是否还在运行
        try:
            import psutil
            if not psutil.pid_exists(pid):
                print(f"进程 {pid} 已结束，端口 {port} 已释放")
            else:
                print(f"进程 {pid} 仍在运行")
        except:
            pass

@shared_task
def start_performance_test_async(task_id, env_id, test_config, executor='system'):
    """异步启动性能测试"""
    try:
        # 获取Web端口配置
        base_port = test_config.get('web_port', 8089)
        
        # 获取本机IP
        host_ip = get_host_ip()
        
        # 使用数据库级别的端口分配
        web_port = get_next_available_port()
        
        # 准备测试数据
        test_data = run_task(task_id, env_id, executor, web_port)
        
        # 立即返回成功，后台异步启动进程
        def start_process_async():
            try:
                # 异步启动Locust进程
                process = subprocess.Popen([
                    'locust', '-f', 'performanceengine/taskGenerate.py', 
                    '--headless', '--web-host', '0.0.0.0', 
                    '--web-port', str(web_port), '--autostart'
                ])
                
                # 记录进程和端口
                _active_processes[process.pid] = web_port
                print(f"后台启动进程成功，PID: {process.pid}, 端口: {web_port}")
                
            except Exception as e:
                print(f"后台启动进程失败: {e}")
        
        # 使用线程异步启动进程
        import threading
        thread = threading.Thread(target=start_process_async)
        thread.daemon = True
        thread.start()
        
        return {
            'success': True,
            'message': '性能测试启动成功',
            'process_pid': process.pid,
            'web_port': web_port,
            'gui_url': f'http://{host_ip}:{web_port}',
            'test_data': test_data
        }
        
    except Exception as e:
        return {
            'success': False,
            'message': f'性能测试启动失败: {str(e)}',
            'error': str(e)
        }


@shared_task
def run_task(task_id, env_id, executor='system', web_port=8089, create_report=True):
    # 获取执行任务的数据
    task = PerformanceTask.objects.get(pk=task_id)
    # 更新任务状态为"执行中"
    task.status = '1'  # 执行中
    task.save()
    task_data = PerformanceTaskRunSerializer(task).data
    presetting = task_data['presetting']
    scene_list = []
    # 初始化report变量
    report = None
    # 导入结果收集模块
    try:
        from performanceengine.taskResult import create_and_monitor_report
        from performanceengine.systemMonitor import start_system_monitoring
        # 创建测试报告 - 使用传入的执行人信息
        timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
        report_name = f"【{task.taskName + '性能报告'}】{timestamp}"
        if create_report:
            report = create_and_monitor_report(task_id, env_id, report_name, executor)
        # 启动系统资源监控
        if report:
            start_system_monitoring(report.id, task_id)
            print(f"已启动系统资源监控")
    except Exception as e:
        print(f"创建报告或启动监控失败: {e}")
        # 如果创建报告失败，更新任务状态为"执行失败"
        task.status = '99'  # 执行失败
        task.save()
        report = None
    for scene in task_data['taskScence']:
        steps = scene['taskscencestep_set']
        # 排序
        steps.sort(key=lambda x: x['sort'])
        def process_item(item):
            processed_item = {**item['stepInfo'], **{'children': []}}
            if 'children' in item:
                processed_item['children'] = [process_item(child) for child in item['children']]
            return processed_item
        scene_list.append({'steps': [process_item(item) for item in steps], 'name': scene['name'], 'env': scene['env'][0] if scene['env'] else None, 'weight': scene['weight']})
    data = {'scenes': scene_list, 'presetting': {**presetting},'env': env_id, 'report_id': report.id if report else None, 'web_port': web_port}
    # 如果报告存在，更新报告的端口信息
    if report:
        report.web_port = web_port
        report.save()
    # 将 data 存储到 params.py 文件
    save_data(data)
    return data


def kill_process_using_port(port):
    """杀掉占用指定端口的进程"""
    import platform
    import time
    
    try:
        if platform.system() == "Darwin":  # macOS
            # 在macOS上查找并杀掉占用端口的进程
            result = subprocess.run(f"lsof -ti :{port}", shell=True, capture_output=True, text=True)
            if result.stdout.strip():
                pids = result.stdout.strip().split('\n')
                for pid in pids:
                    if pid.strip():
                        try:
                            subprocess.run(f"kill -9 {pid.strip()}", shell=True, check=True)
                            print(f"Killed process with PID {pid.strip()} on port {port}")
                        except subprocess.CalledProcessError as e:
                            print(f"Failed to kill process {pid.strip()}: {e}")
            else:
                print(f"No process found using port {port}")
                
        elif platform.system() == "Linux":
            # Linux系统
            try:
                # 先尝试温和的方式
                result = subprocess.run(f"fuser -k {port}/tcp", shell=True, capture_output=True, text=True)
                print(f"Killed processes on port {port}")
            except:
                # 如果失败，尝试更强制的方式
                result = subprocess.run(f"fuser -k -9 {port}/tcp", shell=True, capture_output=True, text=True)
                print(f"Force killed processes on port {port}")
            
        else:  # Windows
            # Windows系统
            try:
                # 查找占用端口的进程
                result = subprocess.run(f"netstat -ano | findstr :{port}", shell=True, capture_output=True, text=True)
                lines = result.stdout.strip().split('\n')
                
                if not lines or lines[0] == '':
                    print(f"No process found using port {port}.")
                    return
                    
                for line in lines:
                    parts = line.split()
                    if len(parts) >= 5:
                        pid = parts[-1]
                        try:
                            # 先尝试温和的方式
                            subprocess.run(f"taskkill /PID {pid} /F", shell=True, check=True)
                            print(f"Terminated process with PID {pid} occupying port {port}.")
                        except subprocess.CalledProcessError:
                            # 如果失败，尝试强制方式
                            try:
                                os.kill(int(pid), 9)
                                print(f"Force terminated process with PID {pid} occupying port {port}.")
                            except Exception as e:
                                print(f"Error terminating process: {e}")
            except Exception as e:
                print(f"Error in Windows process killing: {e}")
        
        # 等待一下让进程完全退出
        time.sleep(1)
        
        # 验证端口是否已释放
        import socket
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind(('0.0.0.0', port))
            sock.close()
            print(f"端口 {port} 已成功释放")
        except OSError:
            print(f"警告: 端口 {port} 可能仍被占用")
                        
    except Exception as e:
        print(f"Error killing process on port {port}: {e}")